Render As You Fetchパターン
#宣言的UIの設計レシピ #宣言的UIのデータフェッチ #Reactのデータフェッチパターン
@housecor: Problem: Fetching in useEffect means React components render, then fetch. This can lead to slow network waterfalls.
Solution: Fetch as you render. Use react-query's prefetching (prefetch in the parent) or use @remix_run which does this by default via nested routes.
#react
https://pbs.twimg.com/media/FUF8tZDUEAEEGeY.jpg
useEffectによるデータフェッチはアンチパターン
このアンチパターン(Fetch Then Render)を克服するために生まれた
裏にある理論
Suspense
関数型プログラミング
Effect System
Algebraic Effects
Extensible Effects
モナド
IOモナド
Readerモナド
遅延結合と遅延初期化とリソース更新/破棄の自動化
要点
Web APIの結果をインメモリでキャッシュして取り回すパターン
キャッシュキーの生成や管理手法は自由で、WeakMapだったり文字列だったり様々
この辺に秩序をもたらす設計論もある
Orvalとか
コンポーネントはマウント時に、キャッシュマネージャーを介し透過的にAPIレスポンスを読み取る
キャッシュがあるとき: キャッシュからレスポンスを得る
キャッシュがないとき: データフェッチして、その結果をキャッシュに格納する
得られるメリット
データ取得ライブラリを SPA に導入するとなぜ嬉しいのか
【Jetpack Compose】React HooksとSWRに学ぶ宣言的データフェッチのすすめ
SWRのような仕組みを用意して使うとケース次第ではAAC ViewModelどころかRepositoryパターンすら不要と言えるでしょう。
ReadModelの副作用に対して冪等性が得られる
キャッシュの有無に関わらず、コンポーネントのレンダリングの結果は同じになる
コンポーネントの純粋度が高まる = レンダリングの予測性を高める
RepositoryレイヤやリモートデータとUIの状態遷移パターンを取っ払える
↓みたいなのを考えなくてよくなる
ユニオン型(union type)によるローディングやエラーハンドリング
Race Condition
setLoading、駆逐してやる!!この世から...一匹残らず...!【進撃のデータフェッチ】
複雑GUIにおけるRepositoryパターンの勘所
#React が出典なはず
React HooksとSuspenseを組み合わせる
データフェッチ→レスポンス返ってくるまでsuspend→返ってきたらレンダリング再開
ローディングなどの処理をそのコンポーネントの外に任せることができる
型レベルでローディングの状態を追い出すことができ、宣言的に非同期データを扱える
この流れはAlgebraic Effectsを応用したものと言われている
「コンポーネントがpromiseをthrowする = 継続を取ってこれる例外である」
promiseを一種の規約と見なしで副作用を分離するという一種のExtensible Effects
Readerモナドとかとはまた違う概念っぽい? #わかってないこと
コード例
代表的な実装例でいうとTanStack Query
将来的にはReactが公式にReact cache apiでサポートする予定
@HFarooq22: @N_Tepluhina
showing in one function how to solve 5 issues that would’ve taken me a week to figure out (if at all 🙏🏼)
https://scrapbox.io/files/641cd857ad2680001c74491b.png
このuseQueryに全てが詰め込まれている
1. API呼び出し
2. Selector
3. refetch on window focus
4. place holder
5. stale time
6. loading, error
上記の例ではSuspenseは使われていない
オプトインする形でuseQueryのオプションにsuspense: trueを追加するとSuspenseモードになる
従来との差分
old
親がマウント時にuseEffectでデータフェッチして子にpropsで流していた
レスポンスが返ってくるまではフラグ管理などで子コンポーネントをマウントしないなどで制御する
痛み
考慮する状態が増える
マウント時のレスポンスは空
フェッチ後に保存される
検索条件を変えたら再度フェッチ
並列レンダリング制御が難しい
リクエスト数の増加やパフォーマンスの低下
複数APIを扱う時にローディングやAPIを呼び出す順番やレスポンスの正規化で悩む
ローディング管理などでコンポーネント階層が深くなりがち
状態を減らしたい
after
子がデータフェッチを行い親はSuspenseやError Boundaryで非同期をハンドリングする
関心事の分離
renderフェーズの処理は走る
データフェッチが終わってから、子がレンダリングされる
考慮する状態が減る
ローディング中はそもそもレンダリングされない
依存フェッチの仕組みによって、イベントハンドラで手続き的な再フェッチ処理をしなくとも良い
useEffectが出てこなくなるので再レンダリング回数も減る
コンポーネント階層を浅くできる
UIT INSIDEのReduxからSuspenseに移行したときの話
https://uit-inside.linecorp.com/episode/130
制約
キャッシュされたレスポンスは結果整合性を持つ
トレードオフ
キャッシュキーによるキャッシュ管理が必要
本質的にキャッシュ管理は難しい
いつキャッシュを破棄する, いつ明示的に再取得するなど
CRUDの状態遷移などでキャッシュが古くなった場合、開発者は明示的にキャッシュ破棄する必要性がある
そうしないとキャッシュ破棄を忘れて古いデータが表示される
管理画面などでは多少速度や利便性を犠牲にしてでも強整合性のほうが向いている場合もある
技術の螺旋を乗り越えてうまれたものなので、一度試してみる価値はある
設計上の利点
コンポーネントの見通しがよくなる
Server Stateからundefinedを消すことができる
Race Conditionを抑えられる
データフェッチに冪等性ができる
キャッシュを正しく使っていればRead onlyの画面にStateはいらない
/teramotodaiki/useSWRに出会うまでは、fetchの回数をなるべく減らしたいと思っていた
データとロジックは近い位置にまとめることができる
高凝集で保守性の高いコンポーネントになる
リグレッション管理が楽になる
犠牲的アーキテクチャの実現に役立つ
React > Concurrent Featuresと連携してパフォーマンスに優れた設計が容易
Lifting state upと相性がよく、段階的にスケールが可能
並列レンダリング, ローディング, エラーを親で制御できる
勘所
Render As You FetchとThe five UI states
データフェッチにおいてonSuccess、onError、onSettledはアンチパターン